using Assets.Scripts.Inventory;
using Assets.Scripts.Objects;
using Assets.Scripts.Objects.Electrical;
using Assets.Scripts.Objects.Entities;
using Assets.Scripts.Objects.Items;
using Assets.Scripts.Objects.Pipes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using VisualmodBep;

namespace StationeersServerPreference
{
    public class SspPrefab
    {
        public class Group : IComparable<Group>
        {
            public string name; // should be unique
            public List<string> prefabs = new List<string>();
            public Helper.StringMatcher matcher;
            public ushort priority;
            public int index = -1; // auto generated

            public Group(string name) : this(name, null, null) { }

            public Group(string name, Helper.StringMatcher matcher)
            {
                this.name = name;
                this.matcher = matcher;
                this.priority = 10;
            }

            public Group(string name, List<string> exact, List<string> match, ushort priority = 10, StringComparison compare = StringComparison.Ordinal)
            {
                this.name = name;
                this.matcher = new Helper.StringMatcher(exact, match, compare);
                this.priority = priority;
            }

            public int Count { get => prefabs.Count; }

            public void Add(string prefab)
            {
                if (prefabs.Contains(prefab))
                {
                    Log.LogInfo($"Group Add \"{prefab}\" already in \"{name}\"");
                }
                else
                {
                    prefabs.Add(prefab);
                }
            }

            public IEnumerable<string> GetEnumerable()
            {
                return prefabs;
            }

            public IEnumerable<string> Concat(IEnumerable<string> e)
            {
                return prefabs.Concat(e);
            }

            public IEnumerable<string> Concat(Group group)
            {
                return prefabs.Concat(group.prefabs);
            }

            public int CompareTo(Group other)
            {
                int p = priority.CompareTo(other.priority);
                return p == 0 ? name.CompareTo(other.name) : p;
            }
        }

        private static readonly List<Group> groups = new List<Group>();
        private static readonly Dictionary<string, int> nameToGroupId = new Dictionary<string, int>();
        private static readonly Dictionary<string, int> prefabToGroupId = new Dictionary<string, int>();

        private static readonly Group otherGroup = new Group("Other"); // ensure existance

        public static IEnumerable<Group> AllGroups()
        {
            return groups;
        }

        public static IEnumerable<string> AllPrefabs(bool sorted = true)
        {
            if (sorted)
            {
                List<string> prefabs = prefabToGroupId.Keys.ToList();
                if (sorted)
                {
                    prefabs.Sort();
                }
                return prefabs;
            }
            else
            {
                return prefabToGroupId.Keys.AsEnumerable();
            }
        }

        // not sorted by priority
        public static void AddGroup(params Group[] groups)
        {
            IEnumerable<string> names = from g in SspPrefab.groups select g.name;
            foreach (Group group in groups)
            {
                if (names.Contains(group.name))
                {
                    Log.LogError($"SspPrefab AddGroup group \"{group.name}\" exists");
                }
                else if (String.IsNullOrEmpty(group.name))
                {
                    Log.LogError($"SspPrefab AddGroup new group must have name");
                }
                else
                {
                    SspPrefab.groups.Add(group);
                }
            }
        }

        private static void AddPrefabToGroup(Group group, string prefabName)
        {
            group.Add(prefabName); // should not be null
            prefabToGroupId.Add(prefabName, group.index);
        }

        private static void AddPrefabToGroup(string groupName, string prefabName)
        {
            Group group = FindGroup(groupName);
            if (null == group)
            {
                Log.LogError($"SspPrefab AddPrefabToGroup group \"{groupName}\" not found");
                return;
            }
            AddPrefabToGroup(group, prefabName);
        }

        public static void AddPrefab(string prefabName)
        {
            if (prefabToGroupId.ContainsKey(prefabName))
            {
                Log.LogInfo($"SspPrefab AddPrefab \"{prefabName}\" exists");
                return;
            }

            bool addByTypeCalled = false;
            foreach (Group group in groups)
            {
                if (!addByTypeCalled && group.priority > 20)
                {
                    addByTypeCalled = true;
                    if (TryAddPrefabByType(prefabName))
                    {
                        return;
                    }
                }
                if (group.matcher.Matches(prefabName))
                {
                    AddPrefabToGroup(group, prefabName);
                    return;
                }
            }
            if (!addByTypeCalled)
            {
                TryAddPrefabByType(prefabName);
            }
            else
            {
                AddPrefabToGroup(otherGroup, prefabName); // fallback
            }
        }

        /// @return true if add successfully
        private static bool TryAddPrefabByType(string prefabName)
        {
            DynamicThing thing = Prefab.Find<DynamicThing>(prefabName);
            if (thing == null)
            {
                return false;
            }
            // Battery
            if (thing as BatteryCell != null)
            {
                AddPrefabToGroup("Battery", prefabName);
                return true;
            }
            // Equipment
            if (thing as CharacterItem != null)
            {
                AddPrefabToGroup("Equipment", prefabName);
                return true;
            }
            // Food
            if (thing as Food != null)
            {
                AddPrefabToGroup("Food", prefabName);
                return true;
            }
            // Seed/Plant
            if (thing as Seed != null)
            {
                AddPrefabToGroup("Seed", prefabName); // Seed: Plant
                return true;
            }
            else if (thing as Npc != null || thing as Plant != null)
            {
                AddPrefabToGroup("Plant", prefabName);
                return true;
            }
            // SprayCan
            if (thing as SprayCan != null)
            {
                AddPrefabToGroup("SprayCan", prefabName);
                return true;
            }
            // ToolBelt/HandTool/OtherTool
            if (thing as ToolBelt != null)
            {
                AddPrefabToGroup("ToolBelt", prefabName);
                return true;
            }
            else if (thing as Tool != null)
            {
                AddPrefabToGroup("HandTool", prefabName);
                return true;
            }
            else if (thing as WearableItem != null)
            {
                AddPrefabToGroup("OtherTool", prefabName);
                return true;
            }
            return false;
        }

        private static bool inited = false;

        private static void InitializeFirstTime()
        {
            otherGroup.matcher = new Helper.StringMatcher(new List<string> { "ImGuiCircuitboardAirlockControl" });
            otherGroup.priority = 0;
            // Priority Options
            //  0: Deprecated, Wreckage, Other
            //  1: Suit, Material
            //  5: Canister, StructureFrame, Pipe, Weapon
            //  6: PipeHeat, PipeVent
            // 10: (default)
            // 20: <type based guess>
            // 30: Kit
            // fallback: Other
            AddGroup(
                otherGroup,
                new Group("Battery"),
                new Group("Canister",
                    new List<string> { "CrateMkII", "DynamicCrate", "ItemDirtCanister", "ItemHighVolumeGasCanisterEmpty", "ItemKitDynamicGasTankAdvanced", "ItemWaterBottle" },
                    new List<string> { "^DynamicGas", "^Dynamic(MKII)?LiquidCanister", "^ItemGasCanister", "^ItemKitDynamic.*Canister$", "^ItemLiquidCanister" },
                    5
                ),
                new Group("Circuitboard",
                    new List<string> { "ItemDataDisk", "ItemIntegratedCircuit10" },
                    new List<string> { "^Cartridge", "^Circuitboard", "^ItemSoundCartridge", "^Motherboard" }
                ),
                new Group("CircuitLogic",
                    new List<string> { "ItemCableAnalyser", "ItemKitComputer", "ItemKitConsole", "ItemLiquidPipeAnalyzer", "ItemPipeAnalyizer", "ItemKitPressurePlate", "ItemKitSensor", "ItemKitSpeaker" },
                    new List<string> { "^ItemKitLogic" }
                ),
                new Group("Deprecated",
                    new List<string> { "CircuitboardCameraDisplay", "Character", "ItemAstroloySheets", "ItemScanner", "MotherboardManufacturing" },
                    new List<string> { "^Item.*Insulation$" },
                    0
                ),
                new Group("Equipment",
                    new List<string> { "ItemCreditCard" },
                    new List<string> { "^AccessCard" }
                ),
                new Group("Fabricator",
                    new List<string> { "ItemKitAdvancedPackagingMachine", "ItemKitAutolathe", "ItemKitAutomatedOven", "ItemKitElectronicsPrinter", "ItemKitFabricator", "ItemKitHydraulicPipeBender", "ItemKitSecurityPrinter", "ItemKitToolManufactory" },
                    new List<string> { "^ItemKit.*Furnace$", "Mod$" }
                ),
                new Group("Food",
                    new List<string> { "ItemCocoaPowder", "ItemFlour", "ItemSugar" },
                    new List<string> { "^ItemCooked" }
                ),
                new Group("Glasses",
                    new List<string> { "ItemGlasses", "ItemNVG", "ItemWearLamp" },
                    new List<string> { "^ItemSensor" }
                ),
                new Group("HandTool"),
                new Group("Hydroponics",
                    new List<string> { "ApplianceSeedTray", "ItemHydroponicTray", "ItemKitGrowLight", "ItemKitHarvie", "ItemKitPlanter", "ItemPlantSampler" },
                    new List<string> { "^AppliancePlantGenetic", "DynamicHydroponics$", "^ItemKitHydroponic", "Composter$" }
                ),
                new Group("Ice",
                    new List<string> { "ItemIce", "ItemNitrice", "ItemOxite", "ItemSpaceIce", "ItemVolatiles" },
                    new List<string> { "^ItemPureIce" }
                ),
                new Group("Ingot",
                    new List<string> { "ItemSolidFuel" },
                    new List<string> { "Ingot$" }
                ),
                new Group("Kit",
                    null,
                    new List<string> { "^ItemKit", "^ItemDynamic", "^Kit" },
                    30
                ),
                new Group("Material",
                    new List<string> { "ItemElectronicParts" },
                    new List<string> { "^Item.*Sheets$" },
                    1
                ),
                new Group("Ore",
                    new List<string> { "Fertilizer", "ItemBiomass", "ItemCharcoal" },
                    new List<string> { "^Item.*Ore$" }
                ),
                new Group("OtherTool",
                    new List<string> { "DynamicAirConditioner", "DynamicGPR", "DynamicLight", "DynamicScrubber", "ItemBeacon", "ItemDisposableBatteryCharger", "ItemHandSanitizer", "ItemReusableFireExtinguisher", "ItemRoadFlare" },
                    new List<string> { "DuctTape$" }
                ),
                new Group("Plant",
                    new List<string> { "ItemCropHay" },
                    null
                ),
                new Group("Pipe",
                    new List<string> { "ItemKitInsulatedLiquidPipe", "ItemKitInsulatedPipe", "ItemKitLowVolumeLiquidPipes", "ItemKitLowVolumePipes", "ItemKitPipe", "ItemKitPipeLiquid" },
                    null,
                    5
                ),
                new Group("PipeHeat",
                    new List<string> { "ItemKitAtmospherics", "ItemKitEvaporationChamber" },
                    new List<string> { "^ItemKit.*HeatExchanger$", "^Item(Liquid)?PipeHeater$", "^Item.*WallCooler$", "Radiator(Gas|Liquid)?$" },
                    6
                ),
                new Group("PipeKit",
                    new List<string> { "ItemKitDrinkingFountain", "ItemKitPortablesConnector", "ItemKitPipeOrgan", "ItemKitShower", "ItemLiquidDrain" },
                    new List<string> { "^ItemKit(Insulated)?PipeUtility", "^ItemKit(Liquid)?Tank", "^ItemPipe", "^ItemKit.*Regulator$", "^ItemKitWater", "^ItemKit.*VolumePump$", "^ItemLiquidPipe", "^Item.*TankStorage$", "^ItemWaterPipe" }
                ),
                new Group("PipeVent",
                    null,
                    new List<string> { "^Item.*Vent" },
                    6
                ),
                new Group("Power",
                    new List<string> { "ItemAreaPowerControl", "ItemCableFuse", "ItemFlashingLight", "ItemWallHeater", "ItemWallLight" },
                    new List<string> { "^ItemBatteryCharger", "^ItemCableCoil", "^ItemKitBattery", "^ItemKitPowerTransmitter", "^ItemKitTransformer" },
                    5
                ),
                new Group("PowerGenerator",
                    new List<string> { "ItemKitStirlingEngine", "ItemPowerConnector", "PortableSolarPanel" },
                    new List<string> { "^ItemRTG", "^ItemKitSolarPanel", "^ItemKit.*Turbine$", "Generator$" },
                    5
                ),
                new Group("Seed",
                    new List<string> { "ItemEgg", "ItemFertilizedEgg" },
                    null
                ),
                new Group("SprayCan"),
                new Group("StructureFrame",
                    new List<string> { "ItemKitLadder", "ItemKitRailing" },
                    new List<string> { "^Item.*Frames$", "^ItemKitAirlock", "^ItemKitComposite", "^ItemKit.*Doors?$", "^ItemKitStair", "^ItemKitWall", "Window" },
                    5
                ),
                new Group("Suit",
                    new List<string> { "ItemHat", "ItemJetpackBasic", "ItemMarineBodyArmor", "ItemMiningBackPack", "ItemSpacepack" },
                    new List<string> { "^ItemClothingBagOveralls", "^Item.*EvaSuit$", "^ItemHard", "^Item.*Helmet$", "^Uniform", }
                ),
                new Group("Survival",
                    null,
                    new List<string> { "^ItemPill", "^ItemGasFilter" }
                ),
                new Group("ToolBelt"),
                new Group("Weapon",
                    new List<string> { "FireArmSMG", "FlareGun", "Handgun", "ItemAmmoBox", "ItemExplosive", "ItemGrenade", "ItemLightSword", "ItemMiningCharge", "ItemRemoteDetonator" },
                    new List<string> { "Magazine$", "^Weapon" },
                    5
                ),
                new Group("Wreckage",
                    null,
                    new List<string> { "^ItemWreckage" },
                    0
                )
            );
            Log.LogInfo($"  InitializeFirstTime {groups.Count} groups");
            inited = true;
        }

        // InitializeFirstTime() called only first time, Clear() called each time
        public static void Initialize()
        {
            Log.LogInfo("SspPrefab Initialize");
            if (!inited)
            {
                InitializeFirstTime();
            }
            Clear();
            foreach (string prefabName in InventoryManager.DynamicThingPrefabs)
            {
                AddPrefab(prefabName);
            }
            foreach (Group group in groups)
            {
                group.prefabs.Sort(); // sort prefabs by prefabName
            }
            PrintInfo();
        }

        public static void Clear()
        {
            nameToGroupId.Clear();
            prefabToGroupId.Clear();

            groups.Sort(); // sort by priority
            int i = 0;
            foreach (Group group in groups)
            {
                group.prefabs.Clear();
                group.index = i++;
                nameToGroupId.Add(group.name, group.index);
            }
        }

        public static void PrintInfo()
        {
            Log.LogInfo("SspPrefab PrintInfo DynamicThingPrefabs");
            int i = 0;
            foreach (Group group in groups)
            {
                Log.LogInfo($"  Group {i++:D2} {group.name,15}({group.priority}): {group.Count}");
            }
        }

        public static Group FindGroup(string name)
        {
            return nameToGroupId.ContainsKey(name) ? groups[nameToGroupId[name]] : null;
        }

        public static Group FindPrefabInGroup(string name)
        {
            return prefabToGroupId.ContainsKey(name) ? groups[prefabToGroupId[name]] : null;
        }

        /**
         * @ref WorldManager.InventoryData.Spawn(Thing, ColorSwatch)
         * @see List<string>                            InventoryManager.DynamicThingPrefabs
         * @see Dictionary<int,Thing>                   Prefab._allPrefabs
         * @see Hashset<Thing>                          OcclusionManager.AllThingsHashSet
         * @see ConcurrentDictionary<DynamicThing,byte> OcclusionManager.AllDynamicThings
         * @see void                                    Prefab.Register(Thing sourcePrefab)
         */
        public static string StatThing(Thing thing)
        {
            if (thing == null)
            {
                return $"{thing?.name},,null,,,\n";
            }
            string str = $"{thing.name},{thing.DisplayName},{thing.GetPrefabHash()},";
            // group
            SspPrefab.Group group = SspPrefab.FindPrefabInGroup(thing.name);
            if (group != null)
            {
                str += group.name;
            }

            // type
            string sub = "";
            if (thing as CharacterItem != null) { sub += "&character_item"; }
            if (thing as DeviceImport != null) { sub += "&device_import"; }
            if (thing as Entity != null) { sub += "&entity"; }
            if (thing as Food != null) { sub += "&food"; }
            if (thing as Ingot != null) { sub += "&ingot"; }
            if (thing as Npc != null) { sub += "&npc"; }
            if (thing as Plant != null) { sub += "&plant"; }
            if (thing as Seed != null) { sub += "&seed"; }
            if (thing as SimpleFabricatorBase != null) { sub += "&fabricator"; }
            if (thing as SprayCan != null) { sub += "&spray_can"; }
            if (thing as Structure != null) { sub += "&structure"; }
            if (thing as Tool != null) { sub += "&tool"; }
            if (thing as ToolBelt != null) { sub += "&tool_belt"; }
            if (thing as WearableItem != null) { sub += "&wearable_item"; }
            str += ',' + (sub.Length > 0 ? sub.Substring(1) : "");

            // property
            sub = "";
            Stackable stackable = thing as Stackable;
            if (stackable != null) { sub += $"&stack={stackable.MaxQuantity}"; }
            IQuantity quantity = thing as IQuantity;
            if (quantity != null) { sub += $"&quantity={quantity.GetMaxQuantity}"; }
            BatteryCell batteryCell = thing as BatteryCell;
            if (batteryCell != null) { sub += $"&battery_cell={batteryCell.PowerMaximum}"; }
            WaterBottle waterBottle = thing as WaterBottle;
            if (waterBottle != null) { sub += $"&water={waterBottle.MaxQuantity}"; }
            str += ',' + (sub.Length > 0 ? sub.Substring(1) : "");

            return str;
        }
    }

    namespace Dumper
    {
        public sealed class DynamicThingDumper : DumperBase<DynamicThingDumper>, IDumper
        {
            public override string GetString()
            {
                string buf = "Name,DisplayName,Prefab,Group,Type,Properties\n";
                foreach (string name in InventoryManager.DynamicThingPrefabs)
                {
                    DynamicThing thing = Prefab.Find<DynamicThing>(name);
                    buf += SspPrefab.StatThing(thing) + '\n';
                }
                return buf;
            }
        }

        public sealed class PrefabDumper : DumperBase<PrefabDumper>, IDumper
        {
            public override string GetString()
            {
                string buf = "Name,DisplayName,Prefab,Group,Type,Properties\n";
                foreach (Thing thing in Prefab.AllPrefabs)
                {
                    buf += SspPrefab.StatThing(thing) + '\n';
                }
                return buf;
            }
        }
    }

    namespace Helper
    {
        public class StringMatcher
        {
            public StringComparison CompareFlag;
            public HashSet<string> Exact;
            public HashSet<Regex> Match;

            public StringMatcher(IEnumerable<string> exact = null, IEnumerable<string> match = null, StringComparison compare = StringComparison.Ordinal)
            {
                this.CompareFlag = compare;
                RegexOptions regexOptions = ((int)this.CompareFlag & 1) == 1 ? RegexOptions.IgnoreCase : RegexOptions.None; // @TODO culturial varient
                this.Exact = exact == null ? new HashSet<string>() : new HashSet<string>(exact);
                this.Match = match == null ? new HashSet<Regex>() : new HashSet<Regex>(from r in match select new Regex(r, regexOptions));
            }

            public bool Matches(string str)
            {
                foreach (string s in Exact)
                {
                    if (String.Equals(str, s, CompareFlag))
                    {
                        return true;
                    }
                }
                foreach (Regex r in Match)
                {
                    if (r.IsMatch(str))
                    {
                        return true;
                    }
                }
                return false;
            }
        }
    }
}